/**
 * ISSG Sony China 2011 
 *
 */
package cn.com.dragonsoftbravo.utils.web;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>Struts MessageResources's simple version</p>
 * 
 * Concrete subclass of <code>MessageResources</code> that reads message keys
 * and corresponding strings from named property resources in a
 * <b><i>similar</i></b> manner (see <i>modes</i> below) that
 * <code>java.util.PropertyResourceBundle</code> does. The <code>base</code>
 * property defines the base property resource name, and must be specified.
 * <p>
 * <strong>IMPLEMENTATION NOTE</strong> - This class trades memory for speed by
 * caching all messages located via generalizing the Locale under the original
 * locale as well. This results in specific messages being stored in the message
 * cache more than once, but improves response time on subsequent requests for
 * the same locale + key combination.
 * 
 * 
 * @copyright Promoters Sell-thru System, ISSG, Sony China
 * 
 * @author Dennis Ge
 * @date 2011-4-3
 * @version 0.1
 */

public class MessageResources {

	/** The log. */
	private static Log log = LogFactory.getLog(MessageResources.class);

	/** The config. */
	private String config;

	/** The return null. */
	protected boolean returnNull = false;

	/** The locales. */
	private Map<String, String> locales = new HashMap<String, String>();

	/** The messages. */
	private Map<String, String> messages = new HashMap<String, String>();

	/** The formats. */
	private Map<String, MessageFormat> formats = new HashMap<String, MessageFormat>();

	/**
	 * Instantiates a new message resources.
	 * 
	 * @param config
	 *            the config
	 */
	public MessageResources(String config) {
		this.config = config;
	}

	/**
	 * Gets the message resources.
	 * 
	 * @param config
	 *            the config
	 * @return the message resources
	 */
	public static MessageResources getMessageResources(String config) {

		return new MessageResources(config);
	}

	/**
	 * Gets the message.
	 * 
	 * @param key
	 *            the key
	 * @return the message
	 */
	public String getMessage(String key) {

		return getMessage((Locale) null, key);
	}

	/**
	 * Gets the message.
	 * 
	 * @param locale
	 *            the locale
	 * @param key
	 *            the key
	 * @return the message
	 */
	public String getMessage(Locale locale, String key) {

		String localeKey = localeKey(locale);
		String originalKey = messageKey(localeKey, key);
		String message = null;

		// Search the specified Locale
		message = findMessage(locale, key, originalKey);

		if (message != null) {
			return message;
		}

		// Find the message in the default properties file
		message = findMessage("", key, originalKey);
		if (message != null) {
			return message;
		}

		// Return an appropriate error indication
		if (returnNull) {
			return (null);
		} else {
			return ("???" + messageKey(localeKey(locale), key) + "???");
		}

	}

	/**
	 * Gets the message.
	 * 
	 * @param key
	 *            the key
	 * @param args
	 *            the args
	 * @return the message
	 */
	public String getMessage(String key, Object... args) {
		return this.getMessage((Locale) null, key, args);
	}

	/**
	 * Gets the message.
	 * 
	 * @param locale
	 *            the locale
	 * @param key
	 *            the key
	 * @param args
	 *            the args
	 * @return the message
	 */
	public String getMessage(Locale locale, String key, Object... args) {

		MessageFormat format = null;
		String formatKey = messageKey(locale, key);

		synchronized (formats) {
			format = formats.get(formatKey);
			if (format == null) {
				String formatString = getMessage(locale, key);

				if (formatString == null) {
					return returnNull ? null : ("???" + formatKey + "???");
				}

				format = new MessageFormat(formatString);
				format.setLocale(locale);
				formats.put(formatKey, format);
			}
		}
		return format.format(args);
	}

	/**
	 * Find message.
	 * 
	 * @param locale
	 *            the locale
	 * @param key
	 *            the key
	 * @param originalKey
	 *            the original key
	 * @return the string
	 */
	private String findMessage(Locale locale, String key, String originalKey) {

		String localeKey = localeKey(locale);
		String message = null;
		int underscore = 0;

		// Loop from specific to general Locales looking for this message
		while (true) {
			message = findMessage(localeKey, key, originalKey);
			if (message != null) {
				break;
			}

			// Strip trailing modifiers to try a more general locale key
			underscore = localeKey.lastIndexOf("_");

			if (underscore < 0) {
				break;
			}

			localeKey = localeKey.substring(0, underscore);
		}

		return message;
	}

	/**
	 * Locale key.
	 * 
	 * @param locale
	 *            the locale
	 * @return the string
	 */
	protected String localeKey(Locale locale) {
		return (locale == null) ? "" : locale.toString();
	}

	/**
	 * Message key.
	 * 
	 * @param locale
	 *            the locale
	 * @param key
	 *            the key
	 * @return the string
	 */
	protected String messageKey(Locale locale, String key) {
		return (localeKey(locale) + "." + key);
	}

	/**
	 * Message key.
	 * 
	 * @param localeKey
	 *            the locale key
	 * @param key
	 *            the key
	 * @return the string
	 */
	protected String messageKey(String localeKey, String key) {
		return (localeKey + "." + key);
	}

	/**
	 * Find message.
	 * 
	 * @param localeKey
	 *            the locale key
	 * @param key
	 *            the key
	 * @param originalKey
	 *            the original key
	 * @return the string
	 */
	private String findMessage(String localeKey, String key, String originalKey) {

		// Load this Locale's messages if we have not done so yet
		loadLocale(localeKey);

		// Check if we have this key for the current locale key
		String messageKey = messageKey(localeKey, key);

		// Add if not found under the original key
		boolean addIt = !messageKey.equals(originalKey);

		synchronized (messages) {
			String message = messages.get(messageKey);

			if (message != null) {
				if (addIt) {
					messages.put(originalKey, message);
				}
			}
			return (message);
		}
	}

	/**
	 * Load locale.
	 * 
	 * @param localeKey
	 *            the locale key
	 */
	private void loadLocale(String localeKey) {
		if (log.isTraceEnabled()) {
			log.trace("loadLocale(" + localeKey + ")");
		}

		if (locales.get(localeKey) != null) {
			return;
		}

		locales.put(localeKey, localeKey);

		// Set up to load the property resource for this locale key, if we can
		String name = config.replace('.', '/');

		if (localeKey.length() > 0) {
			name += ("_" + localeKey);
		}

		name += ".properties";

		InputStream is = null;
		Properties props = new Properties();

		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

		if (classLoader == null) {
			classLoader = this.getClass().getClassLoader();
		}

		is = classLoader.getResourceAsStream(name);

		if (is != null) {
			try {
				props.load(is);
			} catch (IOException e) {
				log.error("loadLocale()", e);
			} finally {
				try {
					is.close();
				} catch (IOException e) {
					log.error("loadLocale()", e);
				}
			}
			if (log.isTraceEnabled()) {
				log.trace("  Loading resource completed");
			}
		}

		// Copy the corresponding values into our cache
		if (props.size() < 1) {
			return;
		}

		synchronized (messages) {
			Iterator<Object> names = props.keySet().iterator();
			while (names.hasNext()) {
				String key = (String) names.next();
				messages.put(messageKey(localeKey, key), props.getProperty(key));
			}
		}
	}
}
